import inspect
import traceback

from functools import wraps
from collections import defaultdict

from utils.log import Log
from utils.error import SimpleError


class Profile(Log):
    cache = None

    def __new__(cls, *args, **kwargs):
        instance = super(Profile, cls).__new__(cls)
        if cls.cache is None:
            cls.cache = defaultdict(int)
        return instance

    def __init__(self, end=False, log=False):
        super(Profile, self).__init__()
        self.end = end
        self.log = log

    @staticmethod
    def parameters_to_string(args, kwargs) -> str:
        kw_list = [f"{key}={str(value)}" for key, value in kwargs.items()]
        return ', '.join([str(arg) for arg in args] + kw_list)

    def function_path(self, function):
        return f"{function.__module__}::{function.__qualname__}"

    def cache_key(self, function, args, kwargs):
        # 如果针对默认参数采用的是位置参数,则没法确认,暂时就先注释掉
        # sig = inspect.signature(function)
        # for parameter in sig.parameters.values():
        #     if parameter.name != 'self' and \
        #             not isinstance(parameter.default, type) and \
        #             parameter.name not in kwargs:
        #         kwargs[parameter.name] = parameter.default
        return f"{self.function_path(function)}(" \
               f"{self.parameters_to_string(args, kwargs)})"

    def cache_function(self, function):
        self.cache[self.function_path(function)] += 1

    def __call__(self, function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            is_fail = False
            if len(args) and isinstance(args[0], Log) and hasattr(args[0], function.__name__):
                this = args[0]
                nargs = args[1:]
            else:
                this = Log()
                nargs = args
            try:
                if self.log:
                    self.info(f"{self.cache_key(function, nargs, kwargs)} enter")
                result = function(*args, **kwargs)
                if self.log:
                    self.info(f"{self.cache_key(function, nargs, kwargs)} exit return {result}")
                return result
            except Exception as e:
                is_fail = True
                if isinstance(e, SimpleError):
                    err = e
                else:
                    tb = traceback.format_exc()
                    err = this.simple_error(tb, end=self.end)
                if self.end:
                    return err
                else:
                    raise err
            finally:
                if is_fail:
                    if hasattr(this, "rollback"):
                        this.rollback()
                self.cache_function(function)
                # if self.log:
                #     self.info(f"{self.cache_key(function, nargs, kwargs)} exit")

        return wrapper

    def print_and_reset(self):
        if self.cache:
            for k, v in dict(self.cache).items():
                self.info(f"{k}: {v}")
            Profile.cache = defaultdict(int)

    def __del__(self):
        self.print_and_reset()
